# -*- coding:utf-8 -*-

import time
import struct
from gevent import socket, monkey, Timeout, queue; monkey.patch_all()
import redis
import gevent
from gevent.local import local
from multiprocessing import Process, cpu_count
import os
import devregister
import json
import datetime

# from utils.logger import get_logger
# logger = get_logger("socket-server")


class MyTimeoutException(Exception):
    pass


class SocketServer(object):
    """
    redis 数据结构
    list -> status   '{"type": "login", "key": 鉴权码, "time": "2019-05-28 18:19:18.330817"}'
    set ->  code    保存鉴权码，用于进行鉴权，不会删除
    list -> status  '{"type": "status", "key": 鉴权码, "value": ascii码(十进制), "value2": ascii码(十进制), "time": "2019-05-28 18:19:18.330817"}'
    list -> status   '{"type": "alarm", "key": 鉴权码, "value": ascii码(十进制), "time": "2019-05-28 18:19:18.330817"}'
    list -> status  '{"type": "logout", "key": 鉴权码, "time": "2019-05-28 18:19:18.330817"}'
    """
    def __init__(self, port, delta_time):
        self.redis = redis.Redis(host="localhost", port=6379, db=2)
        self.server_socket = socket.socket()
        self.server_socket.bind(("", port))
        self.server_socket.listen(5)
        self.delta_time = delta_time     # 超过这个时间没有recv，则认为下线，断开socket

    def string_hex(self, s):
        ret = ''
        for c in s:
            ret += ' 0x%02X' % ord(c)
        return ret[1:]

    def get_now(self):
        now = datetime.datetime.now()
        return now.strftime('%Y-%m-%d %H:%M:%S')

    def get_ascii_str(self, s):
        s = '7E' + s + '7E'
        length = len(s)
        raw = ""
        for i in range(length / 2):
            raw += '%c' % eval('0x' + s[2 * i: 2 * i + 2])
        return raw

    def coroutine_request(self, conn, timeout=None):
        msg = "basic response"
        local_data = local()                                      # 将鉴权码放到本协程作用域内，与其他协程隔离
        local_data.authority_code = None                          # 根据鉴权码来区分电梯, 往redis扔数据时带上authority_code
        # local_data.authority_code = u'25557c306635626726546a52763b6d68'    # TODO 临时测试的鉴权码 后面去掉
        while True:
            try:
                with Timeout(timeout, MyTimeoutException):    # 超过心跳时间，如果接受不到数据，异常处理，关闭socket
                    data = conn.recv(1024)
                print data
                if not data:           # client socket 断开
                    print 'no data, gonna quite reading'
                    break
                print 'full msg: %s' % self.string_hex(data)
                if ord(data[0]) != 0x7E:                           # 格式不对
                    print '! unrecognized msg start 0x%02X' % ord(data[0])
                    continue
                msg_type = None
                if ord(data[1]) == 0x0 and ord(data[2]) == 0x2:
                    print '--^-- heart beating ---^--'
                    msg_type = 'heart'
                elif ord(data[1]) == 0x11 and ord(data[2]) == 0x5:
                    print '----- status report ------'
                    msg_type = 'status'
                elif ord(data[1]) == 0x11 and ord(data[2]) == 0x6:
                    print '--!--- alarm ----!--'
                    msg_type = 'alarm'
                elif ord(data[1]) == 0x11 and ord(data[2]) == 0x0:
                    print '--#--- register ----#--'
                    msg_type = 'register'
                elif ord(data[1]) == 0x1 and ord(data[2]) == 0x2:
                    print '--$-- auth ---$--'
                    msg_type = 'auth'

                head = '%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X' % (
                    ord(data[3]), ord(data[4]),
                    ord(data[5]), ord(data[6]), ord(data[7]), ord(data[8]),
                    ord(data[9]), ord(data[10]), ord(data[11]), ord(data[12]))

                print 'msg attribute: 0x%02X 0x%02X' % (ord(data[3]), ord(data[4]))
                msg_len = ord(data[4])
                print 'msg length %d' % msg_len
                print 'sim card number: %02X%02X%02X%02X%02X%02X' % (ord(data[5]), ord(data[6]), ord(data[7]), ord(data[8]),
                                                         ord(data[9]), ord(data[10]))
                sim_no = '%02X%02X%02X%02X%02X%02X' % (ord(data[5]), ord(data[6]), ord(data[7]), ord(data[8]),
                                                       ord(data[9]), ord(data[10]))
                print 'msg sn: 0x%02X 0x%02X' % (ord(data[11]), ord(data[12]))
                sn = '%02X%02X' % (ord(data[11]), ord(data[12]))
                if msg_type == 'heart':
                    # 心跳应答
                    if not local_data.authority_code:
                        authority_code = devregister.get_authority_code(sim_no)
                        local_data.authority_code = authority_code
                    if not local_data.authority_code:
                        # msg = sn + "7102" + "01" + devregister.get_now()
                        msg = "7102" + head + "01" + devregister.get_now()
                        conn.send(self.get_ascii_str(msg))
                        print "there is not authority_code at beating so i will kill it"
                        break
                    else:
                        # msg = sn + "7102" + "00" + devregister.get_now()
                        print "beat is effective so baby keep alive"
                        msg = "7102" + head + "00" + devregister.get_now()
                elif msg_type == 'status':
                    print "authority_code-------status"
                    print local_data.authority_code
                    print '%02X-%02X-%02X %02X:%02X:%02X' % (ord(data[18]), ord(data[17]), ord(data[16]), ord(data[15]),
                                                             ord(data[14]), ord(data[13]))
                    print 'status: 0x%02X, floor: 0x%02X' % (ord(data[19]), ord(data[24]))
                    print "run_times: Ox%02X%02X" % (ord(data[26]), ord(data[25]))
                    status = ord(data[19])
                    status2 = ord(data[21])
                    floor = ord(data[23])
                    # run_times = ord(data[25])
                    rts = "%02X%02X" % (ord(data[26]), ord(data[25]))
                    run_times = int(rts, 16)
                    if not local_data.authority_code:
                        authority_code = devregister.get_authority_code(sim_no)
                        local_data.authority_code = authority_code
                    if not local_data.authority_code:
                        # msg = sn + "1105" + "02"
                        msg = "8001" + head + "01"
                        conn.send(self.get_ascii_str(msg))
                        break
                    else:
                        # msg = sn + "1105" + "00"
                        msg = "8001" + head + "00"
                        dic = {"type": "status", "key": local_data.authority_code, "value": status, "value2": status2,
                               "time": self.get_now(), "floor": floor, "run_times": run_times}
                        self.redis.lpush("status", json.dumps(dic))
                elif msg_type == 'alarm':
                    print "authority_code--------alarm"
                    print local_data.authority_code
                    print '%02X-%02X-%02X %02X:%02X:%02X' % (ord(data[18]), ord(data[17]), ord(data[16]), ord(data[15]),
                                                             ord(data[14]), ord(data[13]))
                    print 'status: 0x%02X' % (ord(data[19]))
                    t = '%02X-%02X-%02X %02X:%02X:%02X' % (ord(data[18]), ord(data[17]), ord(data[16]), ord(data[15]),
                                                           ord(data[14]), ord(data[13]))
                    alarm = ord(data[19])
                    alarm2 = ord(data[21])
                    if not local_data.authority_code:
                        authority_code = devregister.get_authority_code(sim_no)
                        local_data.authority_code = authority_code
                    if not local_data.authority_code:
                        # msg = sn + "1106" + "02"
                        msg = "8001" + head + "01"
                        conn.send(self.get_ascii_str(msg))
                        break
                    else:
                        # msg = sn + "1106" + "00"
                        msg = "8001" + head + "00"
                        dic = {"type": "alarm", "key": local_data.authority_code, "value": alarm, "value2": alarm2,
                               "time": self.get_now()}
                        self.redis.lpush("status", json.dumps(dic))

                elif msg_type == 'register':
                    print 'device type: %X%X%X%X%X%X%X%X' % (ord(data[13]), ord(data[14]), ord(data[15]), ord(data[16]),
                                                             ord(data[17]), ord(data[18]), ord(data[19]), ord(data[20]))
                    print 'manufacturer: %X%X%X%X%X%X%X%X' % (ord(data[21]), ord(data[22]), ord(data[23]), ord(data[24]),
                                                             ord(data[25]), ord(data[26]), ord(data[27]), ord(data[28]))
                    print 'device sn:: %X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X' % \
                          (ord(data[30]), ord(data[29]), ord(data[32]), ord(data[31]), ord(data[34]), ord(data[33]),
                           ord(data[36]), ord(data[35]), ord(data[38]), ord(data[37]), ord(data[40]), ord(data[39]),
                           ord(data[42]), ord(data[41]), ord(data[44]), ord(data[43]))

                    device_type = '%02X%02X%02X%02X%02X%02X%02X%02X' % (
                        ord(data[13]), ord(data[14]), ord(data[15]), ord(data[16]),
                        ord(data[17]), ord(data[18]), ord(data[19]), ord(data[20]))
                    manufacturer = '%02X%02X%02X%02X%02X%02X%02X%02X' % (
                        ord(data[21]), ord(data[22]), ord(data[23]), ord(data[24]),
                        ord(data[25]), ord(data[26]), ord(data[27]), ord(data[28]))
                    device_sn = '%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X' % \
                          (ord(data[30]), ord(data[29]), ord(data[32]), ord(data[31]), ord(data[34]), ord(data[33]),
                           ord(data[36]), ord(data[35]), ord(data[38]), ord(data[37]), ord(data[40]), ord(data[39]),
                           ord(data[42]), ord(data[41]), ord(data[44]), ord(data[43]))

                    code, b = devregister.register(device_type, manufacturer, device_sn, sim_no)
                    if not code:
                        result = "02"
                        msg = '7100' + head + result
                    elif b:
                        result = "00"
                        msg = '7100' + head + result + code
                        local_data.authority_code = code
                    else:
                        result = "01"
                        msg = '7100' + head + result + code
                        local_data.authority_code = code
                elif msg_type == 'auth':
                    authority_code = '%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x' % \
                          (ord(data[13]), ord(data[14]), ord(data[15]), ord(data[16]), ord(data[17]), ord(data[18]),
                           ord(data[19]), ord(data[20]), ord(data[21]), ord(data[22]), ord(data[23]), ord(data[24]),
                           ord(data[25]), ord(data[26]), ord(data[27]), ord(data[28]))
                    print "authority_code: %s" % authority_code
                    b = self.redis.sismember("code", authority_code)
                    if not b:
                        # msg = sn + "0102" + "01"
                        msg = '8001' + head + '01'
                        conn.send(self.get_ascii_str(msg))
                        print "the authority_code input not in redis"
                        break
                    else:
                        local_data.authority_code = authority_code
                        # msg = sn + "0102" + "00"
                        msg = '8001' + head + '00'
                        dic = {"type": "login", "key": local_data.authority_code,
                               "time": self.get_now()}
                        print "dic: %s" % dic
                        self.redis.lpush("status", json.dumps(dic))
                # unp = struct.unpack('5b', data[:5])
                conn.send(self.get_ascii_str(msg))
                print "the msg has been send is: %s" % msg
            except MyTimeoutException:
                print "beat has missed"
                # 将电梯改为离线状态
                if local_data.authority_code:
                    dic = {"type": "logout", "key": local_data.authority_code,
                           "time": self.get_now()}
                    self.redis.lpush("status", json.dumps(dic))
                break
            except Exception as e:
                print 'oh, got exception: %s' % e
                break
        conn.close()

    def process_request(self):
        while True:
            print "father's pid (%s),son's pid(%s)" % (os.getppid(), os.getpid())
            print 'waiting for new connections...'
            conn, addr = self.server_socket.accept()                     # 等待socket连接，此处会阻塞
            print 'here got one, fetch data'
            gevent.spawn(self.coroutine_request, conn, self.delta_time)


def main():
    socket_server = SocketServer(10001, 60)
    # cpu_cores = cpu_count()  # cpu数量，保证每个cpu跑一个进程，在每个进程上面跑协程
    cpu_cores = 2    # 先开两个进程 TODO 后面去掉
    p_list = []
    for i in range(cpu_cores):
        p = Process(target=socket_server.process_request, args=())
        p.start()
        p_list.append(p)
        print "process : %s has been started" % p.pid

    for p in p_list:
        p.join()

    print "this message will never be printed ! never!"


if __name__ == '__main__':
    main()


